这篇内容是在有道云旧笔记基础上重新整理的 JavaScript 与前端开发 笔记。原始记录偏碎片化,这里补充了使用场景、阅读顺序和落地时需要注意的边界,方便以后在项目里快速复用。

适用场景 ​

  • 前端业务中需要快速处理数据、DOM、浏览器能力或通用工具函数时参考。
  • 把散落在项目里的小技巧抽成可复用代码片段,减少重复实现。
  • 排查兼容性和边界输入问题前,先用本文示例确认核心思路。

核心要点 ​

  • 正文以代码或命令片段为主,阅读时建议先确认目标问题,再挑选对应片段验证。
  • 保留了 3 段可直接参考的代码或命令,主要涉及 javascript,复制前需要按当前项目路径、版本和变量名做调整。

静态数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
<!--
* @Author: your name
* @Date: 2019-12-30 14:58:34
* @LastEditTime : 2020-01-04 17:58:29
* @LastEditors : Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: .\echarts\index.html
-->
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>网络监测</title>
<script src="./jquery.js"></script>
<script src="./echarts.min.js"></script>
<script src="./moment.js"></script>
<style>
html,
body,
#myChart {
padding: 0;
margin: 0;
width: 100%;
height: 100%;
}
</style>
</head>

<body>
<div id="myChart"></div>
<script>
console.log(moment().format("YYYY-MM-DD HH:mm:ss"))
const defaultConfig = {
// 数组0 默认比例基数, 数组1 缩放级别后操作数据(添加设备名称显示)
geoZoom: [0, 4],
// 谁被状态 标识
status: [{
status: 0,
name: "损坏",
letter: 'error',
color: "#D82D5D"
},
{
status: 1,
name: "正常",
letter: 'normal',
color: "#26AF24"
},
{
status: 2,
name: "异常",
letter: 'warning',
color: "#FFD022"
}
],
// symbol icon
camera: "path://M554.666667 763.733333V810.666667h128v85.333333H341.333333v-85.333333h128v-46.933334c-145.066667-21.333333-256-145.066667-256-294.4s110.933333-273.066667 256-294.4V170.666667h85.333334v4.266666c145.066667 21.333333 256 145.066667 256 294.4s-110.933333 273.066667-256 294.4zM512 682.666667c119.466667 0 213.333333-93.866667 213.333333-213.333334s-93.866667-213.333333-213.333333-213.333333-213.333333 93.866667-213.333333 213.333333 93.866667 213.333333 213.333333 213.333334z m0-128c-46.933333 0-85.333333-38.4-85.333333-85.333334s38.4-85.333333 85.333333-85.333333 85.333333 38.4 85.333333 85.333333-38.4 85.333333-85.333333 85.333334z"
}
const myChart = echarts.init(document.getElementById("myChart"))

const geoCoordMap = {
centre: [108.9000730000, 34.1876790000],
position: [{
title: "高新区",
value: [108.895935, 34.201035],
status: 1,
ip: '192.68.0.1',
address: '锦业路1号都市之门A座',
target: true
},
{
title: "省图书馆",
value: [108.840267, 34.21774],
status: 1,
ip: '192.68.0.138',
address: '天谷七路与云水一路交汇处西北侧',
target: false
},
{
title: "省训练中心",
value: [108.891147, 34.207829],
status: 2,
ip: '192.68.0.34',
address: '丈八东路311号',
target: false
},
{
title: "电子科技大学",
value: [108.839993, 34.128716],
status: 0,
address: '西沣路兴隆段266号',
ip: '192.68.0.166',
target: false
},
{
title: "软件新城",
value: [108.885038, 34.231172],
status: 1,
ip: '192.68.0.9',
address: '天谷八路211号环普科技产业园',
target: false
},
{
title: "木塔寺公园",
value: [108.901927, 34.216188],
status: 1,
ip: '192.68.0.223',
address: '科技六路6号',
target: false
},
{
title: "华为研究所",
value: [108.845082, 34.204008],
status: 1,
ip: '192.68.0.99',
address: '南郑大道南',
target: false
},
{
title: "中兴科技园",
value: [108.88004, 34.191312],
status: 1,
ip: '192.68.0.87',
address: '唐延南路10号中兴产业园',
target: false
}
]
}

//颜色16进制转RGB方法
const colorRgb = (color = '#fff', opacity = 1) => {
let sColor = color.toLowerCase()
//十六进制颜色值的正则表达式
const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/
// 如果是16进制颜色
if (sColor && reg.test(sColor)) {
if (sColor.length === 4) {
let sColorNew = "#"
for (let i = 1; i < 4; i += 1) {
sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1))
}
sColor = sColorNew
}
//处理六位的颜色值
let sColorChange = []
for (let i = 1; i < 7; i += 2) {
sColorChange.push(parseInt("0x" + sColor.slice(i, i + 2)))
}
return sColorChange.join(",") + "," + opacity
}
return sColor
}
// 设置目标点, 配置线条指向
const convertToLineData = (data, title) => {
if (data && data.length) {
const TDATA = [...data]
const LineData = []
const isTarget = data.filter(i => i.target === true)[0]
if ((isTarget && isTarget.value[0], isTarget.value[1])) {
data.forEach(i => {
if (i.title === title) {
const gps = [i.value[0], i.value[1]]
LineData.push(i.status < 1 ? [{
coord: gps
},
{
coord: []
}
] : [{
coord: gps
},
{
coord: isTarget.value
}
])
}
})
return LineData
}
return []
}
return []
}

const getSeriesData = data => {
let series = []
if (data.position && data.position.length > 0) {
data.position.forEach(i => {
if (i.target === true) {
// 目标点
series.push({
name: '监控中心',
type: "scatter",
coordinateSystem: "geo",
zlevel: 2,
rippleEffect: {
period: 4,
brushType: "stroke",
scale: 4
},
label: {
normal: {
show: true,
position: "right",
offset: [5, 0],
color: "#0f0",
formatter: "{b}",
textStyle: {
color: "#0f0"
}
},
emphasis: {
show: true,
color: "#f60"
}
},
tooltip: {
formatter: `名称: ${i.title}监控中心(${i.ip})<br />地址: ${i.address}`,
backgroundColor: `rgba(${colorRgb('#f60', 0.35)})`,
padding: [5, 12],
borderWidth: 0.5,
borderColor: `rgba(${colorRgb('#f60', 0.4)})`,
textStyle: {
color: `rgba(255, 255, 255, 1)`
},
extraCssText: 'box-shadow: 5px 5px 30px rgba(0, 0, 0, 0.2)'
},
symbol: "pin",
symbolSize: 38,
data: [{
name: i.title,
value: i.value
}]
})
} else {
const status = defaultConfig.status.filter(f => f.status === i.status)[0]
const geoData = [
Object.assign(i, status, {
// symbol: defaultConfig.camera
})
]
series.push({
name: i.name,
type: "lines",
zlevel: 6,
large: true,
effect: {
show: true,
constantSpeed: 40,
symbol: "pin",
symbolSize: 5,
trailLength: 0
},
lineStyle: {
normal: {
color: i.color,
width: 0.8,
opacity: 0.2,
curveness: 0.05
}
},
tooltip: {
formatter: `设备: ${i.title}`,
backgroundColor: `rgba(${colorRgb(i.color, 0.2)})`,
textStyle: {
color: `rgba(255, 255, 255, .85)`
},
padding: [5, 12],
borderWidth: 0.5,
borderColor: `rgba(${colorRgb(i.color, 0.3)})`,
extraCssText: 'box-shadow: 3px 3px 20px rgba(0, 0, 0, 0.1)'
},
data: convertToLineData(data.position, i.title)
}, {
name: i.name,
type: "effectScatter",
coordinateSystem: "geo",
zlevel: 2,
rippleEffect: {
brushType: "stroke"
},
label: {
normal: {
color: "rgba(255, 255, 255, 0.6)",
// formatter: "{b}",
formatter({
data
}) {
return data.title;
},
position: "top",
show: false
}
},
tooltip: {
formatter: `设备: ${i.title}(${i.ip})<br />状态: ${i.name}<br />地址: ${i.address}`,
backgroundColor: `rgba(${colorRgb(i.color, 0.35)})`,
padding: [5, 12],
borderWidth: 0.5,
borderColor: `rgba(${colorRgb(i.color, 0.4)})`,
textStyle: {
color: `rgba(255, 255, 255, 1)`
},
extraCssText: 'box-shadow: 5px 5px 30px rgba(0, 0, 0, 0.2)'
},
symbolSize: 6,
showEffectOn: "render",
itemStyle: {
normal: {
color: i.color
}
},
data: geoData
})
}
})

// 环形统计
series.push({
type: 'pie',
center: ['82%', '25%'],
radius: ['17.6%', '20%'],
tooltip: {
trigger: 'item',
formatter: "{b} : {c} ({d}%)"
},
label: {
normal: {
formatter: '{b}\n{d}%'
},

},
data: defaultConfig.status.map(i => {
const vd = data.position.filter(f => f.status === i.status).length
return ({
value: vd,
name: i.name,
tooltip: {
show: false
},
itemStyle: {
normal: {
color: `rgba(${colorRgb(i.color, 0.8)})`,
shadowBlur: 30,
shadowColor: `rgba(${colorRgb(i.color, 0.1)})`
}
},
label: {
normal: {
textStyle: {
color: `rgba(${colorRgb(i.color, 0.8)})`,
},
fontSize: 14
}
}
})
})
})
}
return series
}

// 解析地图数据包
$.getJSON("/geodata.json", geoJson => {
// 注册地图,传入数据
echarts.registerMap("echarts", geoJson)
const options = {
backgroundColor: "#152643",
title: {
text: "网络监控数据图",
subtext: "数据纯属虚构",
left: 50,
top: 50,
textStyle: {
lineHeight: 25,
fontSize: 18,
fontWeight: 'normal',
color: "#fff"
}
},
graphic: {
elements: [{
type: 'text',
style: {
text: '25',
fontSize: 30,
fill: "#fff"
},
width: '',
right: '17%',
top: '24%'
}]
},
tooltip: {
trigger: "item"
},
// tooltip: {
// trigger: "item",
// renderMode: 'richText',
// formatter ({ color, componentSubType, data }) {
// // { color, componentSubType, data }
// return `{fline| 设备:|data.title}\n{tline| 名称:132132132132132132}\n{tline| 状态:132132132132132132}`
// },
// position: 'top',
// backgroundColor: 'rgba(50,50,50,0)',
// padding: [4, 12],
// borderRadius: 2,
// lineHeight: 32,
// color: '#f7fafb',
// rich:{
// fline: {
// padding: [0, 10, 15, 10],
// color:'#ffffff'
// },
// tline: {
// padding: [10, 10, 0, 10],
// color:'#ffffff'
// }
// }
// },
legend: {
orient: "vertical",
bottom: '15%',
right: '30%',
icon: 'circle',
data: defaultConfig.status.map(i => Object.assign(i, {
textStyle: {
color: 'rgba(255, 255, 255, .6)'
}
})),
textStyle: {
color: "#fff"
},
selectedMode: 'multiple'
},
geo: {
map: "echarts",
center: geoCoordMap.centre,
label: {
emphasis: {
show: false
}
},
scaleLimit: {
min: 1,
max: 20
},
roam: true,
itemStyle: {
normal: {
borderColor: 'rgba(26,82,231, 0.8)',
borderWidth: 1,
areaColor: {
type: 'radial',
x: 0.5,
y: 0.5,
r: 0.8,
colorStops: [{
offset: 0,
color: 'rgba(14, 101, 247, .1)' // 0% 处的颜色
}, {
offset: 1,
color: 'rgba(125, 183, 252, .1)' // 100% 处的颜色
}],
globalCoord: false // 缺省为 false
},
shadowColor: 'rgba(255, 255, 255, 0)',
shadowOffsetX: -2,
shadowOffsetY: 2,
shadowBlur: 10
},
emphasis: {
areaColor: 'rgba(14, 101, 247, .12)',
borderColor: 'rgba(26,82,231, 0.6)'
}
}
},
series: getSeriesData(geoCoordMap)
}
let geoZoom = defaultConfig.geoZoom[0]

// myChart.on('legendselectchanged', (params) => {
// let option = myChart.getOption()
// const selected = params.selected
// let series = []
// for (const key in selected) {
// if (selected.hasOwnProperty(key)) {
// const element = selected[key]
// if (element === true) {
// series = getSeriesData(geoCoordMap).filter(a => a.name === key || a.name === 'target')
// }
// }
// }
// option.series = series
// console.log(series, option)
// myChart.setOption(option)

// })
myChart.on("georoam", params => {
const zoom = params.zoom
let thatOption = myChart.getOption()
let series = [...thatOption.series]

// 显示/隐藏 series数据中 label
series = thatOption.series.map(i => {
if (i.label.show === geoZoom > defaultConfig.geoZoom[1] ? false : true && i.type ===
'effectScatter') {
i.label.show = geoZoom > defaultConfig.geoZoom[1] ? true : false
i.symbolSize = geoZoom > defaultConfig.geoZoom[1] ? 18 : 6
}
return i
})
console.log('series', series)
thatOption.series = series

if (zoom > 1) {
geoZoom++
} else {
if (zoom > 0 && geoZoom > 0) {
geoZoom--
}
}

myChart.setOption(thatOption)
})

myChart.setOption(options)
window.addEventListener("resize", () => {
myChart.resize()
})
})
</script>
</body>

</html>

动态数据(使用websocket) 通信方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
<!--
* @Author: your name
* @Date: 2019-12-30 14:58:34
* @LastEditTime : 2020-01-06 16:35:43
* @LastEditors : Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: .\echarts\index.html
-->
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>网络监测</title>
<script src="./jquery.js"></script>
<script src="./echarts.min.js"></script>
<style>
html,
body,
#myChart {
padding: 0;
margin: 0;
width: 100%;
height: 100%;
}
</style>
</head>

<body>
<div id="myChart"></div>
<script>
const defaultConfig = {
// 数组0 默认比例基数, 数组1 缩放级别后操作数据(添加设备名称显示)
geoZoom: [0, 4],
// 谁被状态 标识
status: [{
status: 0,
name: "损坏",
letter: 'error',
color: "#D82D5D"
},
{
status: 1,
name: "正常",
letter: 'normal',
color: "#26AF24"
},
{
status: 2,
name: "异常",
letter: 'warning',
color: "#FFD022"
}
],
// symbol icon
camera: "path://M554.666667 763.733333V810.666667h128v85.333333H341.333333v-85.333333h128v-46.933334c-145.066667-21.333333-256-145.066667-256-294.4s110.933333-273.066667 256-294.4V170.666667h85.333334v4.266666c145.066667 21.333333 256 145.066667 256 294.4s-110.933333 273.066667-256 294.4zM512 682.666667c119.466667 0 213.333333-93.866667 213.333333-213.333334s-93.866667-213.333333-213.333333-213.333333-213.333333 93.866667-213.333333 213.333333 93.866667 213.333333 213.333333 213.333334z m0-128c-46.933333 0-85.333333-38.4-85.333333-85.333334s38.4-85.333333 85.333333-85.333333 85.333333 38.4 85.333333 85.333333-38.4 85.333333-85.333333 85.333334z"
}
const myChart = echarts.init(document.getElementById("myChart"))

//颜色16进制转RGB方法
const colorRgb = (color = '#fff', opacity = 1) => {
let sColor = color.toLowerCase()
//十六进制颜色值的正则表达式
const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/
// 如果是16进制颜色
if (sColor && reg.test(sColor)) {
if (sColor.length === 4) {
let sColorNew = "#"
for (let i = 1; i < 4; i += 1) {
sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1))
}
sColor = sColorNew
}
//处理六位的颜色值
let sColorChange = []
for (let i = 1; i < 7; i += 2) {
sColorChange.push(parseInt("0x" + sColor.slice(i, i + 2)))
}
return sColorChange.join(",") + "," + opacity
}
return sColor
}
// 设置目标点, 配置线条指向
const convertToLineData = (data, title) => {
if (data && data.length) {
const TDATA = [...data]
const LineData = []
const isTarget = data.filter(i => i.target === true)[0]
if (isTarget) {
data.forEach(i => {
if (i.title === title) {
console.log(i.value)
if (i.value) {
const gps = [i.value[0], i.value[1]]
LineData.push(i.status < 1 ? [{
coord: gps
},
{
coord: []
}
] : [{
coord: gps
},
{
coord: isTarget.value
}
])
}
}
})
return LineData
}
return []
}
return []
}

const getSeriesData = data => {
let series = []
if (data.position && data.position.length > 0) {
data.position.forEach(i => {
if (i.target === true) {
// 目标点
series.push({
name: '监控中心',
type: "scatter",
coordinateSystem: "geo",
zlevel: 2,
rippleEffect: {
period: 4,
brushType: "stroke",
scale: 4
},
label: {
normal: {
show: true,
position: "right",
offset: [5, 0],
color: "#0f0",
formatter: "{b}",
textStyle: {
color: "#0f0"
}
},
emphasis: {
show: true,
color: "#f60"
}
},
tooltip: {
formatter: `名称: ${i.title}监控中心(${i.ip})<br />地址: ${i.address}`,
backgroundColor: `rgba(${colorRgb('#f60', 0.35)})`,
padding: [5, 12],
borderWidth: 0.5,
borderColor: `rgba(${colorRgb('#f60', 0.4)})`,
textStyle: {
color: `rgba(255, 255, 255, 1)`
},
extraCssText: 'box-shadow: 5px 5px 30px rgba(0, 0, 0, 0.2)'
},
symbol: "pin",
symbolSize: 38,
data: [{
name: i.title,
value: i.value
}]
})
} else {
const status = defaultConfig.status.filter(f => f.status === i.status)[0]
const geoData = [
Object.assign(i, status, {
// symbol: defaultConfig.camera
})
]
series.push({
name: i.name,
type: "lines",
zlevel: 1,
large: true,
effect: {
show: true,
constantSpeed: 40,
symbol: "pin",
symbolSize: 5,
trailLength: 0
},
lineStyle: {
normal: {
color: i.color,
width: 0.8,
opacity: 0.2,
curveness: 0.05
}
},
tooltip: {
formatter: `设备: ${i.title}`,
backgroundColor: `rgba(${colorRgb(i.color, 0.2)})`,
textStyle: {
color: `rgba(255, 255, 255, .85)`
},
padding: [5, 12],
borderWidth: 0.5,
borderColor: `rgba(${colorRgb(i.color, 0.3)})`,
extraCssText: 'box-shadow: 3px 3px 20px rgba(0, 0, 0, 0.1)'
},
data: convertToLineData(data.position, i.title)
}, {
name: i.name,
type: "effectScatter",
coordinateSystem: "geo",
zlevel: 2,
rippleEffect: {
brushType: "stroke"
},
label: {
normal: {
color: "rgba(255, 255, 255, 0.6)",
// formatter: "{b}",
formatter({
data
}) {
return data.title;
},
position: "top",
show: false
}
},
tooltip: {
formatter: `设备: ${i.title}(${i.ip})<br />状态: ${i.name}<br />地址: ${i.address}`,
backgroundColor: `rgba(${colorRgb(i.color, 0.35)})`,
padding: [5, 12],
borderWidth: 0.5,
borderColor: `rgba(${colorRgb(i.color, 0.4)})`,
textStyle: {
color: `rgba(255, 255, 255, 1)`
},
extraCssText: 'box-shadow: 5px 5px 30px rgba(0, 0, 0, 0.2)'
},
symbolSize: 6,
showEffectOn: "render",
itemStyle: {
normal: {
color: i.color
}
},
data: geoData
})
}
})
// 环形统计
series.push({
type: 'pie',
center: ['82%', '25%'],
radius: ['17.6%', '20%'],
tooltip: {
trigger: 'item',
formatter: "{b} : {c} ({d}%)"
},
label: {
normal: {
formatter: '{b}\n{d}%'
},

},
data: defaultConfig.status.map(i => {
const vd = data.position.filter(f => f.status === i.status).length
return ({
value: vd,
name: i.name,
tooltip: {
show: false
},
itemStyle: {
normal: {
color: `rgba(${colorRgb(i.color, 0.8)})`,
shadowBlur: 30,
shadowColor: `rgba(${colorRgb(i.color, 0.1)})`
}
},
label: {
normal: {
textStyle: {
color: `rgba(${colorRgb(i.color, 0.8)})`,
},
fontSize: 14
}
}
})
})
})
}
return series
}

const setChartData = (geoCoordMap = []) => {
// let geoCoordMapData = geoCoordMap
// 注意:在echatrs 中 推送当前数据,在使用 echatrs 拖拽或者缩放等交互后会影响部分数值变动,如,坐标(经纬度),改变后会形成数值偏差(当前需求坐标等非特殊情况下不会改变,所以只改变其它数值)
// 进多次测试尝试加可能会出现内存溢出,目前解决问题 使用 echarts 的 setOption() 重新赋值,这样可以 优化部分性能问题
const getMyChart = myChart.getOption()
if (getMyChart && getMyChart.series) {
console.log(getMyChart.series)
const series = getMyChart.series.map(i => {
const ip = i.data[0].ip
const data = geoCoordMap.position.filter(f => f.ip === ip)[0]
if (data && data.ip) {
const status = defaultConfig.status.filter(f => f.status === data.status)[0]
const fields = ['status', 'name', 'color']
const name = status.name
const color = status.color
const oldName = i.name
let formatter = i.tooltip.formatter
if (ip === data.ip) {
i.formatter = formatter.replace(new RegExp(oldName, 'g'), name)
i.name = name
i.data[0].name = name
i.data[0].status = status.status
i.data[0].color = color
i.itemStyle.color = color
i.tooltip.backgroundColor = `rgba(${colorRgb(color, 0.35)})`
i.tooltip.borderColor = `rgba(${colorRgb(color, 0.4)})`
}
}
return
})

// geoCoordMapData.centre = getMyChart.geo[0].center
// geoCoordMap.position.forEach((i, a) => {
// // 通过唯一 ip 关联查询数据
// const pv = getMyChart.series.filter(p => p.data[0].ip === i.ip && p.type === 'effectScatter')
// if (pv && pv.length > 0) {
// geoCoordMapData.position[0].value = pv[0].data[0].value
// }
// })

return
}
const options = {
backgroundColor: "#152643",
title: {
text: "网络监控数据图",
subtext: "数据纯属虚构",
left: 50,
top: 50,
textStyle: {
lineHeight: 25,
fontSize: 18,
fontWeight: 'normal',
color: "#fff"
}
},
// graphic: {
// elements: [{
// type: 'text',
// style: {
// text: '25',
// fontSize: 30,
// fill: "#fff"
// },
// width: '',
// right: '17%',
// top: '24%'
// }]
// },
tooltip: {
trigger: "item"
},
legend: {
orient: "vertical",
bottom: '15%',
right: '10%',
icon: 'circle',
data: defaultConfig.status.map(i => Object.assign(i, {
textStyle: {
color: 'rgba(255, 255, 255, .6)'
}
})),
textStyle: {
color: "#fff"
},
selectedMode: 'multiple'
},
geo: {
map: "echarts",
center: geoCoordMap.centre,
label: {
emphasis: {
show: false
}
},
scaleLimit: {
min: 1,
max: 20
},
roam: true,
itemStyle: {
normal: {
borderColor: 'rgba(26,82,231, 0.8)',
borderWidth: 1,
areaColor: {
type: 'radial',
x: 0.5,
y: 0.5,
r: 0.8,
colorStops: [{
offset: 0,
color: 'rgba(14, 101, 247, .1)' // 0% 处的颜色
}, {
offset: 1,
color: 'rgba(125, 183, 252, .1)' // 100% 处的颜色
}],
globalCoord: false // 缺省为 false
},
shadowColor: 'rgba(255, 255, 255, 0)',
shadowOffsetX: -2,
shadowOffsetY: 2,
shadowBlur: 10
},
emphasis: {
areaColor: 'rgba(14, 101, 247, .12)',
borderColor: 'rgba(26,82,231, 0.6)'
}
}
},
series: getSeriesData(geoCoordMap)
}


let geoZoom = defaultConfig.geoZoom[0]

myChart.on("georoam", params => {
const zoom = params.zoom
let thatOption = myChart.getOption()
let series = [...thatOption.series]

// 显示/隐藏 series数据中 label
series = thatOption.series.map(i => {
if (i.label.show === geoZoom > defaultConfig.geoZoom[1] ? false : true && i.type ===
'effectScatter') {
i.label.show = geoZoom > defaultConfig.geoZoom[1] ? true : false
i.symbolSize = geoZoom > defaultConfig.geoZoom[1] ? 18 : 6
}
return i
})
console.log('series', series)
thatOption.series = series

if (zoom > 1) {
geoZoom++
} else {
if (zoom > 0 && geoZoom > 0) {
geoZoom--
}
}

myChart.setOption(thatOption)
})

myChart.setOption(options)
window.addEventListener("resize", () => {
myChart.resize()
})
}

// 解析地图数据包
$.getJSON("/geodata.json", geoJson => {
if (!geoJson || Object.keys(geoJson).length < 1) {
return console.log('当前地图数据包为空!')
}
// 注册地图,传入数据
echarts.registerMap("echarts", geoJson)
// 异步获取 动态数据
getAsyncData()
})

// 异步 前后端通信(Ajax/WebSocket) AJAXURL为ajax 通信地址,WSURL为 WebSocket 通信地址 已生产环境地址为准
const AJAXURL = '?'
// const WSURL = "ws://"+location.host+"/"+window.location.pathname.split("/")[1]+"/websocket/?"
const WSURL = 'ws://192.168.0.108:8989'
function getAsyncData(TIME = 5000) {
// 此处可加载 其他 (异步) 事件
WebSocketData(TIME)
}
function WebSocketData(TIME) {
if ("WebSocket" in window) {
console.log("您的浏览器支持 WebSocket!")
const RandomStr = Math.random().toString(36).substr(2)

// 打开一个 web socket
const ws = new WebSocket(WSURL)
ws.onopen = function() {
// Web Socket 已连接上,使用 send() 方法发送数据
let sendData = {
'uid': RandomStr,
'action': 'handshaking'
}
console.log(sendData)
ws.send(sendData);
console.log("数据发送中...")
}
ws.onmessage = function (res) {
let data = dataJsonParse(res.data);
console.log(data,"数据已接收...")
if (data.status != 'ok') {
// errorTips('数据')
console.log("已接收到数据,但数据格式有误...")
return
}
setChartData(data.result)
}
ws.onclose = function() {
console.log("连接已关闭...")
}
ws.onerror = function() {
// errorTips('WebSocket')
console.log("ws 通信异常 可尝试刷新页面...")
};
} else {
console.log("您的浏览器不支持 WebSocket! 异步 ajax 读取数据")
ajaxAsyncData(TIME)
}
}

function ajaxAsyncData(TIME) {
console.log('异步(AJAX)请求数据开始')
let TIMER = setInterval( ()=> {
$.ajax({
type: 'GET',
url: AJAXURL,
data: {},
dataType: 'json',
success: function(res) {
let data = dataJsonParse(res.data);
if (data.status != 'ok') {
console.log("已接收到数据,但数据格式有误...")
return
}
setChartData(data.result)
},
error: function(err) {
// errorTips('AJAX')
}
})
}, TIME)
}

function dataJsonParse(data = []) {
data = Object.prototype.toString.call(data) == '[object Object]' ? data : JSON.parse(data)
return data
}

function errorTips(title = '') {
const DOM = '<div class="msg-wrraper"><div class="msg-box"><h2>' + title +
'通信异常,可尝试刷新页面(多次刷新还未解决,请联系系统管理员)...</h2></div></div>'
$(document.body).append(DOM);
}
</script>
</body>

</html>

实践检查清单 &#8203;

  • 把示例函数放进业务前,先补齐空值、异常输入、异步失败和浏览器兼容性测试。
  • 通用工具函数应尽量收敛到项目公共模块,避免多个页面复制出不同版本。
  • 涉及 UI 或浏览器行为时,至少在桌面端和移动端各验证一次关键路径。
  • 涉及接口、服务端或权限逻辑时,补充失败分支和权限边界测试。

复盘小结 &#8203;

这篇笔记更适合作为问题排查或方案落地前的速查材料。后续如果在真实项目中再次遇到同类问题,可以继续把具体版本、报错信息、最终取舍和验证结果补到对应小节里,让它从代码片段逐步沉淀成完整方案。